﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Xml;

namespace Ogdi.InteractiveSdk.WebForms
{
    /// <summary>
    /// Summary description for ScriptDeferFilter
    /// </summary>
    public class ScriptDeferFilter : Stream
    {
        Stream responseStream;
        long position;

        /// <summary>
        /// When this is true, script blocks are suppressed and captured for 
        /// later rendering
        /// </summary>
        bool captureScripts;

        /// <summary>
        /// Holds all script blocks that are injected by the controls
        /// The script blocks will be moved after the form tag renders
        /// </summary>
        StringBuilder scriptBlocks;

        Encoding encoding;

        /// <summary>
        /// Holds characters from last Write(...) call where the start tag did not
        /// end and thus the remaining characters need to be preserved in a buffer so 
        /// that a complete tag can be parsed
        /// </summary>
        char[] pendingBuffer = null;

        public ScriptDeferFilter(HttpResponse response)
        {
            this.encoding = response.Output.Encoding;
            this.responseStream = response.Filter;

            this.scriptBlocks = new StringBuilder(5000);
            // When this is on, script blocks are captured and not written to output
            this.captureScripts = true;
        }

        #region Filter overrides
        public override bool CanRead
        {
            get { return false; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return true; }
        }

        public override void Close()
        {
            this.FlushPendingBuffer();
            responseStream.Close();
        }

        private void FlushPendingBuffer()
        {
            /// Some characters were left in the buffer 
            if (null != this.pendingBuffer)
            {
                this.WriteOutput(this.pendingBuffer, 0, this.pendingBuffer.Length);
                this.pendingBuffer = null;
            }

        }

        public override void Flush()
        {
            this.FlushPendingBuffer();
            responseStream.Flush();
        }

        public override long Length
        {
            get { return 0; }
        }

        public override long Position
        {
            get { return position; }
            set { position = value; }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return responseStream.Seek(offset, origin);
        }

        public override void SetLength(long length)
        {
            responseStream.SetLength(length);
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return responseStream.Read(buffer, offset, count);
        }
        #endregion

        /*Section 508 - Method to add <noscript> tags to resolve accessibility problems*/
        public override void Write(byte[] buffer, int offset, int count)
        {
            // If we are not capturing script blocks anymore, just redirect to response stream
            if (!this.captureScripts)
            {
                this.responseStream.Write(buffer, offset, count);
                return;
            }

            /* 
             * Script and HTML can be in one of the following combinations in the specified buffer:          
             * .....<script ....>.....</script>.....
             * <script ....>.....</script>.....
             * <script ....>.....</script>
             * <script ....>.....</script> .....
             * ....<script ....>..... 
             * <script ....>..... 
             * .....</script>.....
             * .....</script>
             * <script>.....
             * .... </script>
             * ......
             * Here, "...." means html content between and outside script tags
            */

            char[] charBuffer = this.encoding.GetChars(buffer, offset, count);
            String strScript = new String(charBuffer);
            strScript = strScript.Replace("</script>", "</script><NoScript>Script is not loaded</NoScript>");
            this.WriteOutput(strScript);
            return;
        }


        private void WriteOutput(char[] content, int pos, int length)
        {
            if (length == 0) return;

            byte[] buffer = this.encoding.GetBytes(content, pos, length);
            this.responseStream.Write(buffer, 0, buffer.Length);
        }
        private void WriteOutput(string content)
        {
            byte[] buffer = this.encoding.GetBytes(content);
            this.responseStream.Write(buffer, 0, buffer.Length);
        }
    }
}